package com.kova.api.log.aspect;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kova.api.log.annotation.ApiLog;
import com.kova.api.log.entity.ApiExecptionLog;
import com.kova.api.log.entity.ApiOperateLog;
import com.kova.api.utils.IpUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

@Aspect
@Component
@Slf4j
public class ApiLogAspect {

    ThreadLocal<Long> startTime = new ThreadLocal<>();

    private String operVer = "1.0";

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码(可以不需要)
     */
//    @Pointcut("@annotation(com.kova.api.log.annotation.ApiLog)")
//    public void operLogPoinCut() {
//
//    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    //@Pointcut("execution(* com.kova.api.controller..*.*(..))")
    //同时满足才写日志
    @Pointcut("execution(public * com.kova.api.controller.*.*(..)) && @annotation(com.kova.api.log.annotation.ApiLog)")
    //或者用以下方法注解
    //@Pointcut("@annotation(com.kova.api.log.annotation.ApiLog)")
    public void operExceptionLogPoinCut() {

    }

    @Before("operExceptionLogPoinCut()") //在切入点的方法run之前要干的
    public void deBefore(JoinPoint joinPoint) {

        log.info("deBefore===========================");
        //开始时间
        startTime.set(System.currentTimeMillis());

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();//这个RequestContextHolder是Springmvc提供来获得请求的东西
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 记录下请求内容
        log.info("请求URL : " + request.getRequestURL().toString());
        log.info("请求方式 : " + request.getMethod());
        log.info("请求IP : " + request.getRemoteAddr());

        String methodType=request.getMethod();
        String params = "";
        if (methodType.toUpperCase().equals("POST")){
            // 将参数所在的数组转换成json
            params = getPostBodyJsonStr(joinPoint,method);
        }else{
            // 请求的参数
            params = getArgsBodyStr(joinPoint,method);
        }
        log.info("请求参数 : {}",params);

        //下面这个getSignature().getDeclaringTypeName()是获取包+类名的   然后后面的joinPoint.getSignature.getName()获取了方法名
        log.info("请求调用方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

        //记录日志
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //log.info("before == "+format.format(new Date()) + "  ");

    }

    @After("operExceptionLogPoinCut()")
    public void after(JoinPoint joinPoint) throws NoSuchMethodException {
        log.info("after===========================");
        /**
         //用的最多通知的签名
         Signature signature = joinPoint.getSignature();
         MethodSignature msg=(MethodSignature) signature;
         Object target = joinPoint.getTarget();
         //获取注解标注的方法
         Method method = target.getClass().getMethod(msg.getName(), msg.getParameterTypes());
         //通过方法获取到自定义的注解
         //LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);

         //记录日志
         DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         //log.info("After == "+format.format(new Date()) + "  ");
         **/

    }

    /**
     * 正常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     *
     * @param joinPoint 切入点
     * @param keys      返回结果
     */
    @AfterReturning(value = "operExceptionLogPoinCut()", returning = "keys")
    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
        log.info("doAfterReturning===========================");

        log.info("请求完成时间 : " + (System.currentTimeMillis() - startTime.get()));

        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        ApiOperateLog operlog = new ApiOperateLog();
        operlog.setId(UUID.randomUUID().toString().replaceAll("-",""));
        //operlog.setOperId(UuidUtil.get32UUID()); // 主键ID

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        //1.获取目标类上的目标注解（可判断目标类是否存在该注解）
        ApiLog opLog = method.getAnnotation(ApiLog.class);
        if (opLog != null) {
            String operModul = opLog.module();
            String operType = opLog.type();
            String operDesc = opLog.desc();
            operlog.setModule(operModul); // 操作模块
            operlog.setType(operType); // 操作类型
            operlog.setDesc(operDesc); // 操作描述
        }
        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = method.getName();
        methodName = className + "." + methodName;

        operlog.setMethod(methodName); // 请求方法

        String methodType=request.getMethod();

        String params = "";
        if (methodType.toUpperCase().equals("POST")){
            // 将参数所在的数组转换成json
            params = getPostBodyJsonStr(joinPoint,method);
        }else{
            // 请求的参数
            params = getArgsBodyStr(joinPoint,method);
        }

        operlog.setReqParam(params); // 请求参数
        operlog.setRespParam(JSON.toJSONString(keys)); // 返回结果

        //operlog.setUserId(user.getId()); // 请求用户ID
        //operlog.setUserName(user.getUsername()); // 请求用户名称


        operlog.setIp(IpUtil.getIpAddr(request)); // 请求IP
        operlog.setUri(request.getRequestURI()); // 请求URI request.getRequestURL().toString()
        operlog.setVersion(operVer); // 操作版本
        operlog.setCtime(new Date()); // 创建时间


        log.info("请求完成日志:{}",JSON.toJSONString(operlog));
    }

    /**
     * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     *
     * @param joinPoint 切入点
     * @param e         异常信息
     */
    @AfterThrowing(pointcut = "operExceptionLogPoinCut()", throwing = "e")
    public void throwsMethod(JoinPoint joinPoint, Throwable e) {
        log.info("AfterThrowing===========================");
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);

        ApiExecptionLog excepLog = new ApiExecptionLog();
        excepLog.setId(UUID.randomUUID().toString().replaceAll("-",""));
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        //excepLog.setExcId(UuidUtil.get32UUID());
        // 获取请求的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = method.getName();
        methodName = className + "." + methodName;
        // 请求的参数
        String methodType=request.getMethod();
        // 将参数所在的数组转换成json
        String params = "";
        if (methodType.toUpperCase().equals("POST")){
            // 将参数所在的数组转换成json
            params = getPostBodyJsonStr(joinPoint,method);
        }else{
            // 请求的参数
            params = getArgsBodyStr(joinPoint,method);
        }
        excepLog.setReqParam(params); // 请求参数
        excepLog.setMethod(methodName); // 请求方法名
        excepLog.setName(e.getClass().getName()); // 异常名称
        excepLog.setMessage(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace())); // 异常信息

        //excepLog.setUserId(UserShiroUtil.getCurrentUserLoginName()); // 操作员ID
        //excepLog.setUserName(UserShiroUtil.getCurrentUserName()); // 操作员名称

        excepLog.setUri(request.getRequestURI()); // 操作URI
        excepLog.setIp(IpUtil.getIpAddr(request)); // 操作员IP
        excepLog.setVersion(operVer); // 操作版本号
        excepLog.setCtime(new Date()); // 发生异常时间

        log.info("请求完成发生异常日志:{}",JSON.toJSONString(excepLog));

    }

    /**
     @SneakyThrows
     @Around("operExceptionLogPoinCut()")
     public Object arround(ProceedingJoinPoint pjp) {
     log.info("方法环绕start.....");
     // 进入@before
     Object o =  pjp.proceed();
     log.info("方法环绕proceed，结果完成");
     return o;
     }
     **/


    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    private String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        //String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        String message = exceptionName + ":" + exceptionMessage;
        return message;
    }

    /**
     * 获取post请求参数
     * @param joinPoint
     * @param method
     * @return
     */
    private String getPostBodyJsonStr(JoinPoint joinPoint,Method method){
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        //方法 1 请求的方法参数值 JSON 格式 null不显示
        if (joinPoint.getArgs().length > 0) {
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                //请求参数类型判断过滤，防止JSON转换报错
                if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
                    continue;
                }
                return JSONObject.toJSONString(args[i]);
                //log.info("请求参数名称 :" + paramNames[i] + ", 内容 :" + JSONObject.toJSONString(args[i]));
            }
        }
        return "";
    }
    private String getArgsBodyStr(JoinPoint joinPoint,Method method){
        //请求的方法参数值 兼容fromDate格式和JSON格式
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称 显示所有字段 值为null也显示该字段
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            String params = "";
            for (int i = 0; i < args.length; i++) {
                //请求参数类型判断过滤，
                if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
                    continue;
                }
                params += " " + paramNames[i] + ": " + args[i] + ",";
            }
            //log.info("请求参数 :" + params);
            return params;
        }
        return "";
    }
}
